﻿#region Using directives

using System;
using System.Collections.Generic;
using System.Text;

using Weazel.Gezel.Model;
using Weazel.Gezel.Simulator.Reference;

#endregion

namespace Weazel.Gezel.Simulator.Reference.Setup
{
  public class DatapathSetup
  {
		private static void setupDatapath(
			Simulation sim, 
			Weazel.Gezel.Model.Datapath datapath, 
			Weazel.Gezel.Model.Model model
		)
		{
			// simply skip datapaths which are not part
			// of the simulation
			if (!datapath.InSystem)
				return;

			// also skip datapaths that has already been handled due to recursive calls
			if (sim.datapaths.ContainsKey(datapath.Id))
				return;
			
			// first setup any dependencies 
			foreach (string childDatapathRef in datapath.ChildDatapathRefs.Keys)
			{
				if (model.Datapaths.ContainsKey(childDatapathRef))
				{
					setupDatapath(sim, model.Datapaths[childDatapathRef], model);
				}
				else if (model.IPBlocks.ContainsKey(childDatapathRef))
				{					
					IpBlockSetup.setupIpBlock(sim, model.IPBlocks[childDatapathRef], model);
				}
				else
				{
					string message = String.Format(
						"Simulation Setup: Failed to find child datapath or IP block '{0}' of '{1}' in datapath registry.",
						childDatapathRef,
						datapath.LocalName
						);

					throw new KeyNotFoundException(message);
				}				
			}
			
			Symid datapathId = datapath.Id;
			Datapath d = new Datapath(datapathId, datapath.Name);
			sim.datapaths.Add(datapathId, d);

			foreach (InPort inport in datapath.InPorts.Values)
			{
				Symid inportId = inport.Id;

				InputSignal i = new InputSignal(inportId, inport.Width, inport.Signed ? 1 : 0, inport.Name); // todo: refactor
				sim.signals[inportId] = i;
				d.appendInput(i);
			}

			foreach (OutPort outport in datapath.OutPorts.Values)
			{
				Symid outportId = outport.Id;

				OutputSignal o = new OutputSignal(sim, d.datapathContext, outportId, outport.Width, outport.Signed ? 1 : 0, outport.Name); // todo: refactor
				sim.signals[outportId] = o;
				d.appendOutput(o);
			}

			foreach (Weazel.Gezel.Model.Signal signal in datapath.Signals.Values)
			{
				Signal i = new Signal(sim, d.datapathContext, signal.Id, signal.Width, signal.Signed ? 1 : 0, signal.Name, true); // todo clean up
				sim.signals[signal.Id] = i;
			}

			foreach (Register register in datapath.Registers.Values)
			{
				RegisterSignal i = new RegisterSignal(sim, d.datapathContext, register.Id, register.Width, register.Signed ? 1 : 0, register.Name); // todo clean up
				sim.signals[register.Id] = i;
				sim.symbolNames[register.Id] = register.Name; // store register name (debug purpose)				
				d.appendRegister(i);
			}

			foreach (Gezel.Model.LookupTable lookup in datapath.LookupTables.Values)
			{
				sim.lookupTables[lookup.Id] = lookup;
			}

			foreach (SignalTrace tracer in datapath.Tracers)
			{
				Expression expr = tracer.Expression;
				ExpressionSetup.setupExpression(sim, expr, Symid.NOSYMBOL, d, datapath);
				int exprId = expr.ExpressionType == ExpressionType.Identifier ?
					SimulationSetup.getRefId(datapath, expr as LeafExpression) : expr.Id;
				sim.tracers[tracer.Id] = new TracerBlock(tracer.Filename, sim.signals[exprId], sim.dump);
			}

			// setup parent-children signal connections
			foreach (Weazel.Gezel.Model.Datapath.Net net in datapath.Nets)
			{				
				if (!sim.signals.ContainsKey(net.InPort.Id))
				{
					string message = 
						string.Format(
							"Simulation Setup: Could not find input signal '{0}' (id: '{1}') during setup of " +
							"parent-child (parent: '{2}')." , 
							net.InPort.LocalName, 
							net.InPort.Id,
							datapath.LocalName
						);

					throw new KeyNotFoundException(message);
				}

				if (!sim.signals.ContainsKey(net.OutPort.Id))
				{
					string message =
						string.Format(
							"Simulation Setup: Could not find output signal '{0}' (id: '{1}') during setup of " +
							"parent-child (parent: '{2}').",
							net.InPort.LocalName,
							net.InPort.Id,
							datapath.LocalName
						);

					throw new KeyNotFoundException(message);
				}

				sim.signals[net.InPort.Id].defineDriver(sim.signals[net.OutPort.Id]);
			}

			foreach (SignalFlowGraph sfg in datapath.SignalFlowGraphs.Values)
			{
				Symid sfgid = sfg.Id;

				// Handle sfg local signal
				if (sfg.Signal != null)
				{
					Signal i = new Signal(sim, d.datapathContext, sfg.Signal.Id, sfg.Signal.Width, sfg.Signal.Signed ? 1 : 0, sfg.Signal.Name, true); // todo clean up
					sim.signals[sfg.Signal.Id] = i;
				}

				sim.symbolNames[sfgid] = sfg.Name;
				foreach (Expression expr in sfg.Expressions)
					ExpressionSetup.setupExpression(sim, expr, sfgid, d, datapath);
			}

			// if datapath contains child datapath, add references to them
			foreach (string childDatapathRef in datapath.ChildDatapathRefs.Keys)
			{
				if (model.Datapaths.ContainsKey(childDatapathRef))
				{
					Symid childDatapathId = model.Datapaths[childDatapathRef].Id;
					d.appendChildDatapath(sim.datapaths[childDatapathId]);
				}
				else if (model.IPBlocks.ContainsKey(childDatapathRef))
				{
					Symid childIpBlockId = model.IPBlocks[childDatapathRef].Id;
					d.appendChildIpBlock(sim.IpBlocks[childIpBlockId]);
				}
				else
				{
					string message = String.Format(
						"Simulation Setup: Failed to find child datapath or IP block '{0}' of '{1}' in datapath registry.",
						childDatapathRef,
						datapath.LocalName
						);

					throw new KeyNotFoundException(message);
				}
			}
		}

    internal static void setupDatapaths(Simulation sim, Weazel.Gezel.Model.Model model)
    {
			foreach (Weazel.Gezel.Model.Datapath datapath in model.Datapaths.Values)
				setupDatapath(sim, datapath, model);
    }
  }
}
